﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CokoNES.Emulator.Hardware
{
    public class Cpu6502
    {
        #region Registers, stacks and other stuff from the main processor

        /// <summary>
        /// Program Counter (compteur ordinal)
        /// </summary>
        public ushort PC;

        /// <summary>
        /// Stack Register (pointeur de pile)
        /// </summary>
        public Stack<byte> CallStack;
        
        /// <summary>
        /// 
        ///     Processor Status (registre d'etat)
        /// 	
        ///     +-----+-----+-----+-----+-----+-----+-----+-----+
	    /// 	|  N  |  V  |     |  B  |  D  |  I  |  Z  |  C  |
        /// 	+-----+-----+-----+-----+-----+-----+-----+-----+
        /// 	
        /// 	C - Bit 0, Carry Flag (Bit de retenu): comme son nom l'indique ce bit va permettre de mémoriser
		/// 	si le résultat d'une opération est sujet à une retenue. Un exemple simple:
		/// 	Imaginons que nous ajoutions 1 à 255, cela aura pour résultat 0 avec le bit de retenue à 1.
		/// 	En outre d'autres instructions comme les instructions de rotation l'utilisent.
        /// 	
	    /// 	Z - Bit 1, Zero Flag (Bit d'etat Zero): Ce bit est mis à 1 lorsque le resultat d'une instruction
		/// 	est 0.
        /// 	
	    /// 	I - Bit 2, Interrupt Disable Flag (Bit d'autorisation d'interruptions): Si ce bit est mis à 1 on bloque
		/// 	les interruptions non masquables (NMI) venant des periphériques (détaillé en partie 5).
        /// 	
	    /// 	D - Bit 3, Decimal Mode (Mode décimal): Si il est mis à 1 indique que l'on est en mode décimal.
		/// 	Pas très utile car limite le nombre de combinaisons pour un octet à 255 valeurs.
		/// 	De plus sur la version du 6502 utilisé sur NES, il n'existe pas de mode décimal effectif!
		/// 	Donc activer ou désactiver ce flag ne servira à rien.
        /// 	
	    /// 	B - Bit 4, Break Command (Bit de Break): Si une interruption est causée par l'instruction BRK,
		/// 	ce bit est mis à 1, il est mis à 0 si il s'agit d'une interruption externe.
        /// 	
	    /// 	V - Bit 6, Overflow Flag (Bit de depassement signé): Ce bit permet de simuler la retenue pour
		/// 	2 nombre pseudos signés. C'est a dire que l'on va prendre le calcul de 2 valeurs
		/// 	sur 7 uniquement, le dernier servant au signe. Par exemple: 01111111+00000001=10000000
		/// 	-> ce calcul ne modifiera pas le flag C mais modifiera le flag V.
	    /// 	
	    /// 	N - Bit 7, Negative Flag (Bit de negation): Ce flag est tres lié au second, il sert au calcul de valeurs
		/// 	signées. En fait ce bit est mis a 1 si le resultat contenu dans l'accumulateur à son bit 7
        /// 	mis à 1.
        /// 	
        /// </summary>
        public class PS
        {
            public byte CarryFlag, ZeroFlag, InterruptDisableFlag, DecimalMode, Break, OverflowFlag, NegativeFlag = 0;
        };

        public PS ps;

        /// <summary>
        /// Accumulator (Accumulateur) A: Il s'agit du registre de travail principal, puisque qu'il va être utilisé
        /// par toutes les instructions utilisant l'unité arithmétique et logique du processeur. C'est à dire que
        /// tous les calculs passeront necessairement par lui.
        /// </summary>
        public byte RegisterA;

        /// <summary>
        /// Registre X: Utilisé pour les adressages indexés et le controle des boucles principalement.
        /// </summary>
        public byte RegisterX;

        /// <summary>
        /// Registre Y: Comparable au registre X mais dispose de moins d'instructions et donc de fonctionnalitées.
        /// </summary>
        public byte RegisterY;

        public enum AddressingMod
        {
            Immediate, 
            Absolute, 
            ZeroPage, 
            IndirectAbsolute,
            AbsoluteIndexedX,
            AbsoluteIndexedY,
            IndexedZeroPageX,
            IndexedZeroPageY,
            IndexedIndirectX,
            IndexedIndirectY,
            IndirectIndexedX,
            IndirectIndexedY, 
            Relative, 
            Implicit
        }

        #endregion

        public Cpu6502()
        {
            this.CallStack = new Stack<byte>();
            this.ps = new PS();
        }

        /// <summary>
        /// BRK - Break
        /// </summary>
        public void Opcode_00()
        {
            //this.PC = RAM.R[0xFFFE];
            //this.ps.InterruptDisableFlag = 1;
            //this.ps.Break = 1;

            throw new NotImplementedException();
        }

        /// <summary>
        /// ADC - Add with Carry
        /// </summary>
        public void Opcode_ADC(AddressingMod adressing)
        {
            byte value = 0xFF;

            // Get the value to add
            switch (adressing)
            {
                case AddressingMod.Immediate:
                    {
                        this.PC++;
                        value = RAM.R[this.PC];
                        break;
                    }
                case AddressingMod.ZeroPage:
                    {
                        this.PC++;
                        value = RAM.R[RAM.R[this.PC]];
                        break;
                    }
                case AddressingMod.IndexedZeroPageX:
                    {
                        this.PC++;
                        value = RAM.R[RAM.R[this.PC] + RegisterX];
                        break;
                    }
                case AddressingMod.Absolute:
                    {
                        this.PC++;
                        ushort adr = (ushort)(RAM.R[this.PC] << 8);

                        this.PC++;
                        adr += RAM.R[this.PC];

                        value = RAM.R[adr]; 
                        break;
                    }
                case AddressingMod.AbsoluteIndexedX:
                    {
                        this.PC++;
                        ushort adr = (ushort)(RAM.R[this.PC] << 8);

                        this.PC++;
                        adr += RAM.R[this.PC];

                        adr += RegisterX;

                        value = RAM.R[adr];
                        break;
                    }
                case AddressingMod.AbsoluteIndexedY:
                    {
                        this.PC++;
                        ushort adr = (ushort)(RAM.R[this.PC] << 8);

                        this.PC++;
                        adr += RAM.R[this.PC];

                        adr += RegisterY;

                        value = RAM.R[adr];
                        break;
                    }
                case AddressingMod.IndexedIndirectX:
                    {
                        // TODO Need a test !
                        this.PC++;
                        ushort adr = RAM.R[RAM.R[this.PC + RegisterX]];
                        
                        value = RAM.R[adr];
                        break;
                    }
                case AddressingMod.IndirectIndexedX:
                    {
                        this.PC++;
                        ushort adr = RAM.R[RAM.R[this.PC]];

                        adr += RegisterX;

                        value = RAM.R[adr];
                        break;
                    }

            }

            // Do the operation : carry flag + value added to register A
            uint result = (uint)(this.ps.CarryFlag + value + this.RegisterA);

            // Set flags for PS register
            this.ps.CarryFlag       = result > 0xFF ? (byte)1 : (byte)0;
            this.ps.OverflowFlag    = result > 0x7F ? (byte)1 : (byte)0;
            this.ps.NegativeFlag    = (result & 0x80) == 0x80 ? (byte)1 : (byte)0;
            this.ps.ZeroFlag        = (result & 0xFF) == 0x00 ? (byte)1 : (byte)0;

            // TODO Need a test !
        }
    }
}
